﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Globalization;
using System.Reflection;
using System.Resources;

namespace Kyoh.Lib.Component.Globalization
{
    /// <summary>列挙型のメンバを <see cref="EnumNameAttribute"/> で指定された文字列表現と相互変換する <see cref="EnumConverter"/>の実装です。></summary>
    public class EnumNameConverter : EnumConverter
    {
        ResourceManager typeResource;
        readonly Dictionary<object, EnumField> enums = new Dictionary<object, EnumField>();

        /// <summary><see cref="EnumNameConverter"/> クラスの新しいインスタンスを作成します。</summary>
        /// <param name="type">変換対象の列挙型の <see cref="Type"/> を指定します。</param>
        public EnumNameConverter(Type type)
            : base(type)
        {
            // 変換対象の列挙型の情報を取得する。
            // 型に関連づけられたリソース（ResourceManager）。存在しない場合はnull
            typeResource = GetResource(GetAttribute(type));

            // 列挙型のメンバ（値）の情報を取得し、辞書化する
            foreach (var field in type.GetFields())
            {
                // 列挙値はStaticメンバ。
                if (field.IsStatic)
                {
                    var enumField = new EnumField(typeResource, type.Name, type.FullName, field);
                    enums.Add(enumField.Value, enumField);
                }
            }
        }

        private class EnumField
        {
            public EnumField(ResourceManager typeResource, string typeName, string typeFullName, FieldInfo info)
            {
                // 列挙値名
                _fieldName = info.Name;
                // 列挙型名 + 列挙値名
                _shortName = typeName + "_" + info.Name;
                // 名前空間 + 列挙型名 + 列挙値名
                _fullName = typeFullName.Replace('.', '_') + "_" + info.Name;

                var attr = GetAttribute(info);
                // リソース名
                _resourceName = attr == null ? null : attr.ResourceName;
                // リソース本体
                _resource = GetResource(attr) ?? typeResource;

                Value = info.GetValue(null);
            }

            public object Value { get; private set; }

            private ResourceManager _resource;
            private string _resourceName;
            private string _fieldName;
            private string _fullName;
            private string _shortName;

            /// <summary>列挙型の文字列表現を取得します。</summary>
            public string GetName(CultureInfo culture)
            {
                if (_resource == null)
                    return _resourceName ?? _fieldName;

                string fullName = null, fieldName = null;
                var resourceSet = _resource.GetResourceSet(culture, true, true);
                foreach (DictionaryEntry entry in resourceSet)
                {
                    var resName = entry.Key as string;
                    var value = entry.Value as string;
                    if (resName == null)
                        continue;

                    if (resName == _resourceName)
                        return value;
                    if (resName == _fullName)
                        fullName = value;
                    if (resName == _fieldName)
                        fieldName = value;
                }
                return fullName ?? fieldName ?? _resourceName ?? _fieldName;
            }
        }

        private static EnumNameAttribute GetAttribute(MemberInfo info)
        {
            var attrs = Attribute.GetCustomAttributes(info, typeof(EnumNameAttribute));
            return attrs.Length > 0 ? (EnumNameAttribute)attrs[0] : null;
        }

        private static ResourceManager GetResource(EnumNameAttribute attr)
        {
            return attr == null || attr.ResourceSource == null ? null : new ResourceManager(attr.ResourceSource);
        }

        /// <summary>列挙型メンバの文字列表現を列挙型のメンバ値に変換します。</summary>
        /// <param name="context">書式指定コンテキストを提供する <see cref="ITypeDescriptorContext"/>。</param>
        /// <param name="culture">オプションの <see creg="CultureInfo"/>。指定されなかった場合は、現在のカルチャが使用されます。</param>
        /// <param name="valueToConvert">変換対象の <see cref="Object"/>。<see cref="String"/> 型以外のオブジェクトが指定された場合には、標準の変換を行います。</param>
        /// <returns>変換後の列挙型のメンバ値。</returns>
        public override object ConvertFrom(ITypeDescriptorContext context,
                                           CultureInfo culture,
                                           object valueToConvert)
        {
            string value = valueToConvert as string;
            if (value != null)
                foreach (var field in enums)
                    if (field.Value.GetName(culture) == value)
                        return field.Key;
            return base.ConvertFrom(context, culture, valueToConvert);
        }

        /// <summary>列挙型のメンバを文字列表現に変換します。</summary>
        /// <param name="context">書式指定コンテキストを提供する <see cref="ITypeDescriptorContext"/>。</param>
        /// <param name="culture">オプションの <see creg="CultureInfo"/>。指定されなかった場合は、現在のカルチャが使用されます。</param>
        /// <param name="value">変換対象の <see cref="Object"/>。</param>
        /// <param name="destinationType">変換対象の型を表す <see cref="Type"/>。<see cref="String"/> 以外の型が指定された場合は、標準の変換を行います。</param>
        /// <returns>変換後の値。</returns>
        public override object ConvertTo(ITypeDescriptorContext context,
                                         CultureInfo culture,
                                         object value,
                                         Type destinationType)
        {
            EnumField field;
            if (destinationType == typeof(string))
                if (enums.TryGetValue(value, out field))
                    return field.GetName(culture);
            return base.ConvertTo(context, culture, value, destinationType);
        }
    }
}